using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Contracts;

namespace Tycho.Parser.Peg {
    public class SyntaxErrorRecoverer : ISyntaxErrorRecoverer {
        private readonly ISyntaxErrorLogger SyntaxErrorLogger;
        private List<IProduction> RecoveryPoints;
        private ImmutableStack<RecoveryPoint> ImmutableRecoveryPoints;

        public IEnumerable<RecoveryPoint> CurrentRecoveryPoints {
            get {
                return ImmutableRecoveryPoints.CurrentItems;
            }
        }

        public SyntaxErrorRecoverer (ISyntaxErrorLogger syntaxErrorLogger) {
            SyntaxErrorLogger = syntaxErrorLogger;
            ImmutableRecoveryPoints = new ImmutableStack<RecoveryPoint> ();
        }

        public bool Recovering { get; private set; }

        public bool InRecovery {
            get { return RecoveryPoints != null; }
        }

        public SyntaxErrorRecoveryException Recover (char [] source, int startIndex, ParseContext context, string sourceString, ParseEnvironment environment, IProduction production) {
            Recovering = true;

            try {
                if (RecoveryPoints == null) {
                    throw new Exception ("not in recovery mode");
                }

                int index = startIndex;

                while (index < source.Length) {
                    foreach (var recoveryPoint in Enumerable.Reverse(RecoveryPoints)) {
                        ParseResult result = recoveryPoint.Parse (source, index, context, sourceString, environment);

                        if (result != null) {
                            return CreateRecovery (startIndex, index, context, environment, production, recoveryPoint);
                        }
                    }

                    index++;
                }

                return CreateRecovery (startIndex, index, context, environment, production, null);
            } finally {
                Recovering = false;
            }
        }

        private SyntaxErrorRecoveryException CreateRecovery (int startIndex, int index, ParseContext context, ParseEnvironment environment, IProduction production, IProduction recoveryPoint) {
            RecoveryPoints = null;
            var sinfo = environment.SourceFileInformation.CreateSourceInformation (startIndex, index - startIndex);
            var errorTerm = new ErrorTerm (production, sinfo);
            SyntaxErrorLogger.CommitError (errorTerm);
            return new SyntaxErrorRecoveryException (new ParseResult (index, errorTerm, context), recoveryPoint);
        }

        public void AddRecoveryPoint (IProduction recoveryPoint) {
            if (RecoveryPoints != null) {
                RecoveryPoints.Add (recoveryPoint);
            }
        }

        public void BeginRecovery () {
            RecoveryPoints = new List<IProduction> ();
        }

        public void PushRecoveryPoint (RecoveryPoint point) {
            ImmutableRecoveryPoints.Push (point);
        }

        public void PopRecoveryPoint () {
            ImmutableRecoveryPoints.Pop ();
        }
    }
}